/**
 * @fileoverview Tests for the prefer-exponentiation-operator rule
 * @author Milos Djermanovic
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/prefer-exponentiation-operator");
const RuleTester = require("../../../lib/rule-tester/rule-tester");
const parser = require("../../fixtures/fixture-parser");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Create an object for the invalid array
 * @param {string} code source code
 * @param {string} output fixed source code
 * @returns {Object} result object
 * @private
 */
function invalid(code, output) {
	return {
		code,
		output,
		errors: [
			{
				messageId: "useExponentiation",
			},
		],
	};
}

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022 } });

ruleTester.run("prefer-exponentiation-operator", rule, {
	valid: [
		// not Math.pow()
		"Object.pow(a, b)",
		"Math.max(a, b)",
		"Math",
		"Math(a, b)",
		"pow",
		"pow(a, b)",
		"Math.pow",
		"Math.Pow(a, b)",
		"math.pow(a, b)",
		"foo.Math.pow(a, b)",
		"new Math.pow(a, b)",
		"Math[pow](a, b)",
		{
			code: "globalThis.Object.pow(a, b)",
			languageOptions: { ecmaVersion: 2020 },
		},
		{
			code: "globalThis.Math.max(a, b)",
			languageOptions: { ecmaVersion: 2020 },
		},

		// not the global Math
		"/* globals Math:off*/ Math.pow(a, b)",
		"let Math; Math.pow(a, b);",
		"if (foo) { const Math = 1; Math.pow(a, b); }",
		"var x = function Math() { Math.pow(a, b); }",
		"function foo(Math) { Math.pow(a, b); }",
		"function foo() { Math.pow(a, b); var Math; }",

		{
			code: "globalThis.Math.pow(a, b)",
			languageOptions: { ecmaVersion: 2019 },
		},
		{
			code: "globalThis.Math.pow(a, b)",
			languageOptions: { ecmaVersion: 6 },
		},
		{
			code: "globalThis.Math.pow(a, b)",
			languageOptions: { ecmaVersion: 2017 },
		},
		{
			code: `
                var globalThis = bar;
                globalThis.Math.pow(a, b)
            `,
			languageOptions: { ecmaVersion: 2020 },
		},

		"class C { #pow; foo() { Math.#pow(a, b); } }",
	],

	invalid: [
		invalid("Math.pow(a, b)", "a**b"),
		invalid("(Math).pow(a, b)", "a**b"),
		invalid("Math['pow'](a, b)", "a**b"),
		invalid("(Math)['pow'](a, b)", "a**b"),
		invalid("var x=Math\n.  pow( a, \n b )", "var x=a**b"),
		{
			code: "globalThis.Math.pow(a, b)",
			output: "a**b",
			languageOptions: { ecmaVersion: 2020 },
			errors: [
				{
					messageId: "useExponentiation",
					line: 1,
					column: 1,
					endLine: 1,
					endColumn: 26,
				},
			],
		},
		{
			code: "globalThis.Math['pow'](a, b)",
			output: "a**b",
			languageOptions: { ecmaVersion: 2020 },
			errors: [
				{
					messageId: "useExponentiation",
					line: 1,
					column: 1,
					endLine: 1,
					endColumn: 29,
				},
			],
		},

		// able to catch some workarounds
		invalid("Math[`pow`](a, b)", "a**b"),
		invalid("Math[`${'pow'}`](a, b)", "a**b"),
		invalid("Math['p' + 'o' + 'w'](a, b)", "a**b"),

		// non-expression parents that don't require parens
		invalid("var x = Math.pow(a, b);", "var x = a**b;"),
		invalid("if(Math.pow(a, b)){}", "if(a**b){}"),
		invalid("for(;Math.pow(a, b);){}", "for(;a**b;){}"),
		invalid(
			"switch(foo){ case Math.pow(a, b): break; }",
			"switch(foo){ case a**b: break; }",
		),
		invalid("{ foo: Math.pow(a, b) }", "{ foo: a**b }"),
		invalid(
			"function foo(bar, baz = Math.pow(a, b), quux){}",
			"function foo(bar, baz = a**b, quux){}",
		),
		invalid("`${Math.pow(a, b)}`", "`${a**b}`"),

		// non-expression parents that do require parens
		invalid(
			"class C extends Math.pow(a, b) {}",
			"class C extends (a**b) {}",
		),

		// parents with a higher precedence
		invalid("+ Math.pow(a, b)", "+ (a**b)"),
		invalid("- Math.pow(a, b)", "- (a**b)"),
		invalid("! Math.pow(a, b)", "! (a**b)"),
		invalid("typeof Math.pow(a, b)", "typeof (a**b)"),
		invalid("void Math.pow(a, b)", "void (a**b)"),
		invalid("Math.pow(a, b) .toString()", "(a**b) .toString()"),
		invalid("Math.pow(a, b) ()", "(a**b) ()"),
		invalid("Math.pow(a, b) ``", "(a**b) ``"),
		invalid(
			"(class extends Math.pow(a, b) {})",
			"(class extends (a**b) {})",
		),

		// already parenthesised, shouldn't insert extra parens
		invalid("+(Math.pow(a, b))", "+(a**b)"),
		invalid("(Math.pow(a, b)).toString()", "(a**b).toString()"),
		invalid(
			"(class extends (Math.pow(a, b)) {})",
			"(class extends (a**b) {})",
		),
		invalid(
			"class C extends (Math.pow(a, b)) {}",
			"class C extends (a**b) {}",
		),

		// parents with a higher precedence, but the expression's role doesn't require parens
		invalid("f(Math.pow(a, b))", "f(a**b)"),
		invalid("f(foo, Math.pow(a, b))", "f(foo, a**b)"),
		invalid("f(Math.pow(a, b), foo)", "f(a**b, foo)"),
		invalid("f(foo, Math.pow(a, b), bar)", "f(foo, a**b, bar)"),
		invalid("new F(Math.pow(a, b))", "new F(a**b)"),
		invalid("new F(foo, Math.pow(a, b))", "new F(foo, a**b)"),
		invalid("new F(Math.pow(a, b), foo)", "new F(a**b, foo)"),
		invalid("new F(foo, Math.pow(a, b), bar)", "new F(foo, a**b, bar)"),
		invalid("obj[Math.pow(a, b)]", "obj[a**b]"),
		invalid("[foo, Math.pow(a, b), bar]", "[foo, a**b, bar]"),

		// parents with a lower precedence
		invalid("a * Math.pow(b, c)", "a * b**c"),
		invalid("Math.pow(a, b) * c", "a**b * c"),
		invalid("a + Math.pow(b, c)", "a + b**c"),
		invalid("Math.pow(a, b)/c", "a**b/c"),
		invalid("a < Math.pow(b, c)", "a < b**c"),
		invalid("Math.pow(a, b) > c", "a**b > c"),
		invalid("a === Math.pow(b, c)", "a === b**c"),
		invalid("a ? Math.pow(b, c) : d", "a ? b**c : d"),
		invalid("a = Math.pow(b, c)", "a = b**c"),
		invalid("a += Math.pow(b, c)", "a += b**c"),
		invalid(
			"function *f() { yield Math.pow(a, b) }",
			"function *f() { yield a**b }",
		),
		invalid("a, Math.pow(b, c), d", "a, b**c, d"),

		// '**' is right-associative, that applies to both parent and child nodes
		invalid("a ** Math.pow(b, c)", "a ** b**c"),
		invalid("Math.pow(a, b) ** c", "(a**b) ** c"),
		invalid("Math.pow(a, b ** c)", "a**b ** c"),
		invalid("Math.pow(a ** b, c)", "(a ** b)**c"),
		invalid(
			"a ** Math.pow(b ** c, d ** e) ** f",
			"a ** ((b ** c)**d ** e) ** f",
		),

		// doesn't remove already existing unnecessary parens around the whole expression
		invalid("(Math.pow(a, b))", "(a**b)"),
		invalid("foo + (Math.pow(a, b))", "foo + (a**b)"),
		invalid("(Math.pow(a, b)) + foo", "(a**b) + foo"),
		invalid("`${(Math.pow(a, b))}`", "`${(a**b)}`"),

		// base and exponent with a higher precedence
		invalid("Math.pow(2, 3)", "2**3"),
		invalid("Math.pow(a.foo, b)", "a.foo**b"),
		invalid("Math.pow(a, b.foo)", "a**b.foo"),
		invalid("Math.pow(a(), b)", "a()**b"),
		invalid("Math.pow(a, b())", "a**b()"),
		invalid("Math.pow(++a, ++b)", "++a**++b"),
		invalid("Math.pow(a++, ++b)", "a++**++b"),
		invalid("Math.pow(a--, b--)", "a--**b--"),
		invalid("Math.pow(--a, b--)", "--a**b--"),

		// doesn't preserve unnecessary parens around base and exponent
		invalid("Math.pow((a), (b))", "a**b"),
		invalid("Math.pow(((a)), ((b)))", "a**b"),
		invalid("Math.pow((a.foo), b)", "a.foo**b"),
		invalid("Math.pow(a, (b.foo))", "a**b.foo"),
		invalid("Math.pow((a()), b)", "a()**b"),
		invalid("Math.pow(a, (b()))", "a**b()"),

		// unary expressions are exception by the language - parens are required for the base to disambiguate operator precedence
		invalid("Math.pow(+a, b)", "(+a)**b"),
		invalid("Math.pow(a, +b)", "a**+b"),
		invalid("Math.pow(-a, b)", "(-a)**b"),
		invalid("Math.pow(a, -b)", "a**-b"),
		invalid("Math.pow(-2, 3)", "(-2)**3"),
		invalid("Math.pow(2, -3)", "2**-3"),
		invalid("async () => Math.pow(await a, b)", "async () => (await a)**b"),
		invalid("async () => Math.pow(a, await b)", "async () => a**await b"),

		// base and exponent with a lower precedence
		invalid("Math.pow(a * b, c)", "(a * b)**c"),
		invalid("Math.pow(a, b * c)", "a**(b * c)"),
		invalid("Math.pow(a / b, c)", "(a / b)**c"),
		invalid("Math.pow(a, b / c)", "a**(b / c)"),
		invalid("Math.pow(a + b, 3)", "(a + b)**3"),
		invalid("Math.pow(2, a - b)", "2**(a - b)"),
		invalid("Math.pow(a + b, c + d)", "(a + b)**(c + d)"),
		invalid("Math.pow(a = b, c = d)", "(a = b)**(c = d)"),
		invalid("Math.pow(a += b, c -= d)", "(a += b)**(c -= d)"),
		invalid("Math.pow((a, b), (c, d))", "(a, b)**(c, d)"),
		invalid(
			"function *f() { Math.pow(yield, yield) }",
			"function *f() { (yield)**(yield) }",
		),

		// doesn't put extra parens
		invalid("Math.pow((a + b), (c + d))", "(a + b)**(c + d)"),

		// tokens that can be adjacent
		invalid("a+Math.pow(b, c)+d", "a+b**c+d"),

		// tokens that cannot be adjacent
		invalid("a+Math.pow(++b, c)", "a+ ++b**c"),
		invalid("(a)+(Math).pow((++b), c)", "(a)+ ++b**c"),
		invalid("Math.pow(a, b)in c", "a**b in c"),
		invalid("Math.pow(a, (b))in (c)", "a**b in (c)"),
		invalid("a+Math.pow(++b, c)in d", "a+ ++b**c in d"),
		invalid("a+Math.pow( ++b, c )in d", "a+ ++b**c in d"),

		// tokens that cannot be adjacent, but there is already space or something else between
		invalid("a+ Math.pow(++b, c) in d", "a+ ++b**c in d"),
		invalid("a+/**/Math.pow(++b, c)/**/in d", "a+/**/++b**c/**/in d"),
		invalid("a+(Math.pow(++b, c))in d", "a+(++b**c)in d"),

		// tokens that cannot be adjacent, but the autofix inserts parens required for precedence, so there is no need for an extra space
		invalid("+Math.pow(++a, b)", "+(++a**b)"),
		invalid("Math.pow(a, b + c)in d", "a**(b + c)in d"),

		{
			code: "Math.pow(a, b) + Math.pow(c,\n d)",
			output: "a**b + c**d",
			errors: [
				{
					messageId: "useExponentiation",
					line: 1,
					column: 1,
					endLine: 1,
					endColumn: 15,
				},
				{
					messageId: "useExponentiation",
					line: 1,
					column: 18,
					endLine: 2,
					endColumn: 4,
				},
			],
		},
		{
			code: "Math.pow(Math.pow(a, b), Math.pow(c, d))",
			output: "Math.pow(a, b)**Math.pow(c, d)", // tests perform only one autofix iteration, below is the following one
			errors: [
				{
					messageId: "useExponentiation",
					column: 1,
					endColumn: 41,
				},
				{
					messageId: "useExponentiation",
					column: 10,
					endColumn: 24,
				},
				{
					messageId: "useExponentiation",
					column: 26,
					endColumn: 40,
				},
			],
		},
		{
			code: "Math.pow(a, b)**Math.pow(c, d)",
			output: "(a**b)**c**d",
			errors: [
				{
					messageId: "useExponentiation",
					column: 1,
					endColumn: 15,
				},
				{
					messageId: "useExponentiation",
					column: 17,
					endColumn: 31,
				},
			],
		},

		// shouldn't autofix if the call doesn't have exactly two arguments
		invalid("Math.pow()", null),
		invalid("Math.pow(a)", null),
		invalid("Math.pow(a, b, c)", null),
		invalid("Math.pow(a, b, c, d)", null),

		// shouldn't autofix if any of the arguments is spread
		invalid("Math.pow(...a)", null),
		invalid("Math.pow(...a, b)", null),
		invalid("Math.pow(a, ...b)", null),
		invalid("Math.pow(a, b, ...c)", null),

		// shouldn't autofix if that would remove comments
		invalid("/* comment */Math.pow(a, b)", "/* comment */a**b"),
		invalid("Math/**/.pow(a, b)", null),
		invalid("Math//\n.pow(a, b)", null),
		invalid("Math[//\n'pow'](a, b)", null),
		invalid("Math['pow'/**/](a, b)", null),
		invalid("Math./**/pow(a, b)", null),
		invalid("Math.pow/**/(a, b)", null),
		invalid("Math.pow//\n(a, b)", null),
		invalid("Math.pow(/**/a, b)", null),
		invalid("Math.pow(a,//\n b)", null),
		invalid("Math.pow(a, b/**/)", null),
		invalid("Math.pow(a, b//\n)", null),
		invalid("Math.pow(a, b)/* comment */;", "a**b/* comment */;"),
		invalid("Math.pow(a, b)// comment\n;", "a**b// comment\n;"),

		// Optional chaining
		invalid("Math.pow?.(a, b)", "a**b"),
		invalid("Math?.pow(a, b)", "a**b"),
		invalid("Math?.pow?.(a, b)", "a**b"),
		invalid("(Math?.pow)(a, b)", "a**b"),
		invalid("(Math?.pow)?.(a, b)", "a**b"),

		// https://github.com/eslint/eslint/issues/17173
		{
			code: "Math.pow(a, b as any)",
			output: "a**(b as any)",
			languageOptions: {
				parser: require(
					parser(
						"typescript-parsers/exponentiation-with-assertion-1",
					),
				),
			},
			errors: [
				{
					messageId: "useExponentiation",
				},
			],
		},
		{
			code: "Math.pow(a as any, b)",
			output: "(a as any)**b",
			languageOptions: {
				parser: require(
					parser(
						"typescript-parsers/exponentiation-with-assertion-2",
					),
				),
			},
			errors: [
				{
					messageId: "useExponentiation",
				},
			],
		},
		{
			code: "Math.pow(a, b) as any",
			output: "(a**b) as any",
			languageOptions: {
				parser: require(
					parser(
						"typescript-parsers/exponentiation-with-assertion-3",
					),
				),
			},
			errors: [
				{
					messageId: "useExponentiation",
				},
			],
		},
	],
});
